﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Fuse8.DomainFramework.DataAccess;
using Fuse8.DomainFramework.DataAccess.SqlDatabase;
using Fuse8.DomainFramework.Domain;
using Fuse8.DomainFramework.Practices.DataAccess.SqlRepository;
using Fuse8.DomainFramework.Practices.SystemFacade.Extensions;
using Microsoft.Practices.Unity;

namespace Fuse8.DomainFramework.Practices.DataAccess
{
	public class PolicyConfigurator<TEntity, TKey> where TEntity : class, IEntityObject<TKey>, new() where TKey : IComparable<TKey>, IEquatable<TKey>
	{
		private readonly SqlMultiMapperSettings<TEntity> _multiSettings = new SqlMultiMapperSettings<TEntity>();
        
        public string AddSpNameValue { get; private set; }
        public string SaveSpNameValue { get; private set; }
        public string DeleteSpNameValue { get; private set; }
        public string FindOneSpNameValue { get; private set; }
        public string FindAllSpNameValue { get; private set; }
	    public List<string> IgnoreToList { get; private set; }
	    public bool SupportDynamicSql { get; private set; }
        public List<string> IgnoreFromList { get; private set; }

	    public IEntityDataRepository<TEntity, TKey> Repository
	    {
	        get
	        {
	            if (SupportDynamicSql)
	            {
	                var repository = SqlEntityDataRepositoryConfiguration<TEntity, TKey>.CreateMixedRepository();
	                repository.AddSpName = AddSpNameValue;
	                repository.UpdateSpName = SaveSpNameValue;
	                repository.RemoveSpName = DeleteSpNameValue;
	                repository.FindOneSpName = FindOneSpNameValue;
	                repository.FindAllSpName = FindAllSpNameValue;
	                return repository;
	            }
	            else
	            {
	                var repository = SqlEntityDataRepositoryConfiguration<TEntity, TKey>.CreateRepository();
                    if (AddSpNameValue != null)
                    {
                        repository.AddSpName = AddSpNameValue;
                    }
	                if (SaveSpNameValue != null)
	                {
	                    repository.UpdateSpName = SaveSpNameValue;
	                }
	                if (DeleteSpNameValue != null)
	                {
	                    repository.RemoveSpName = DeleteSpNameValue;
	                }
	                if (FindOneSpNameValue != null)
	                {
	                    repository.FindOneSpName = FindOneSpNameValue;
	                }
	                if (FindAllSpNameValue != null)
	                {
	                    repository.FindAllSpName = FindAllSpNameValue;
	                }
	                return repository;
	            }
	        }
	    }

	    public IUnitOfWorkFactory<TEntity> UnitOfWorkFactory { get; private set; }
		public ISqlEntityDataMapper<TEntity, TKey> Mapper { get { return new SqlBaseMapper<TEntity, TKey>(IgnoreToList, IgnoreFromList, _multiSettings); } }

		/// <summary>
		/// Constructor for configurator
		/// </summary>
		/// <param name="connectionString">Connection string</param>
		/// <param name="supportDynamicSql">State of dynamic SQL supporting</param>
		/// <param name="container">Current container</param>
		public PolicyConfigurator(string connectionString, bool supportDynamicSql, IUnityContainer container)
        {
            IgnoreFromList = new List<string>();
            IgnoreToList = new List<string>();

		    SupportDynamicSql = supportDynamicSql;

		    UnitOfWorkFactory = container.IsRegistered<IUnitOfWorkFactory<TEntity>>() || container.IsRegistered(typeof(IUnitOfWorkFactory<>))
				? container.Resolve<IUnitOfWorkFactory<TEntity>>() 
				: SqlEntityDataRepositoryConfiguration<TEntity, TKey>.CreateUnitOfWorkFactory(connectionString);
        }

		/// <summary>
		/// Adding IgnoreTo properties
		/// </summary>
		/// <param name="expression">Expression defines ignoreTo properties</param>
		/// <returns>Current PolicyConfigurator</returns>
		public PolicyConfigurator<TEntity, TKey> IgnoreTo(Expression<Func<TEntity, object>> expression)
        {
			AddPropertiesToList(IgnoreToList, expression);
            return this;
        }

		/// <summary>
		/// Adding IgnoreFrom properties
		/// </summary>
		/// <param name="expression">Expression defines ignoreFrom properties</param>
		/// <returns>Current PolicyConfigurator</returns>
		public PolicyConfigurator<TEntity, TKey> IgnoreFrom(Expression<Func<TEntity, object>> expression)
		{
			AddPropertiesToList(IgnoreFromList, expression);
            return this;
        }
		/// <summary>
		/// Define properties for multi reader
		/// </summary>
		/// <typeparam name="TParameter">Type of CommandParameter</typeparam>
		/// <param name="expression">Expression defines properties for multimapper</param>
		/// <returns>Current PolicyConfigurator</returns>		
		public PolicyConfigurator<TEntity, TKey> AddMultiProperties<TParameter>(Expression<Func<TEntity, object>> expression)
        {
			_multiSettings.AddProperties<TParameter>(expression, _multiSettings.FirstSetIsEntity ? 1 : 0);
            return this;
        }

		/// <summary>
		/// Define property for multi reader
		/// </summary>
		/// <typeparam name="TParameter">Type of CommandParameter</typeparam>
		/// <param name="expression">Expression defines property for multimapper</param>
		/// <param name="index">Index of property in readers collection</param>
		/// <returns>Current PolicyConfigurator</returns>
		public PolicyConfigurator<TEntity, TKey> AddMultiProperty<TParameter>(Expression<Func<TEntity, object>> expression, int index)
        {
			_multiSettings.AddPropertyIndex<TParameter>(index, expression);
            return this;
        }

		/// <summary>
		/// Define first entity as multi reader container
		/// </summary>
		/// <param name="value">Flag</param>
		/// <returns>Current PolicyConfigurator</returns>
		public PolicyConfigurator<TEntity,TKey> FirstMultiReaderSetIsForEntity(bool value = true)
        {
			_multiSettings.FirstSetIsEntity = value;
            return this;
        }

        public PolicyConfigurator<TEntity,TKey> AddSpName(string name)
        {
            AddSpNameValue = name;
            return this;
        }

        public PolicyConfigurator<TEntity, TKey> SaveSpName(string name)
        {
            SaveSpNameValue = name;
            return this;
        }

        public PolicyConfigurator<TEntity, TKey> DeleteSpName(string name)
        {
            DeleteSpNameValue = name;
            return this;
        }

        public PolicyConfigurator<TEntity, TKey> FindOneSpName(string name)
        {
            FindOneSpNameValue = name;
            return this;
        }

        public PolicyConfigurator<TEntity, TKey> FindAllSpName(string name)
        {
            FindAllSpNameValue = name;
            return this;
        }

		private static void AddPropertiesToList(List<string> propertiesList, Expression<Func<TEntity, object>> expression)
		{
			switch (expression.Body.NodeType)
			{
				case ExpressionType.MemberAccess:
				case ExpressionType.Convert:
					propertiesList.Add(expression.MemberName());
					break;
				case ExpressionType.New:
					propertiesList.AddRange(((NewExpression)expression.Body).Members.Select(p => p.Name));					
					break;
				default:
					throw new ArgumentOutOfRangeException("expression");
			}
		}
	}
}